/**
 * 
 */
package yagwl.client.widget;

import java.util.logging.Logger;

import yagwl.client.model.TreeCache;
import yagwl.client.model.DataReadyEventBus;
import yagwl.client.model.ItemDTO;
import yagwl.client.model.TreeEvent;
import yagwl.client.model.TreeEvent.TreeEventHandler;
import yagwl.client.model.TreeNodeDTO;
import yagwl.client.model.TreeNodeEvent;
import yagwl.client.model.TreeNodeEvent.TreeNodeEventHandler;
import yagwl.client.resource.CellTableResources;
import yagwl.client.resource.Resources;
import yagwl.client.widget.cell.ImageActionCell;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Tree;
import com.google.gwt.user.client.ui.TreeItem;
import com.google.gwt.user.client.ui.VerticalPanel;

/**
 * Builds on GWT Tree and adds tree management capabilities
 */
public class ManagedTree extends Composite implements TreeEventHandler,
    TreeNodeEventHandler {

  private static final Logger log = Logger.getLogger(ManagedTree.class
      .getName());
  private final ScrollingSplitPanel rootSP = new ScrollingSplitPanel(200);
  private final Tree tree = new Tree();
  private final CellTable<ItemDTO> cellTable = new CellTable<ItemDTO>(15,
      CellTableResources.R);
  private final HorizontalPanel noItemsPanel = new HorizontalPanel();
  private final ConfirmDialogBox confDialog = new ConfirmDialogBox();
  private TreeCache treeCache;
  private AbstractSelectItemDialogBox selectItemDialogBox;
  private AbstractTreeNodeUpdateDialogBox treeNodeUpdateDialogBox;
  private boolean treeNodeCreateEnabled;
  private boolean treeNodeUpdateLabelEnabled;
  private boolean treeNodeUpdateEnabled;
  private boolean treeNodeDeleteEnabled;
  private boolean treeNodeItemAddEnabled;
  private boolean treeNodeItemDeleteEnabled;
  private String treeNodeCreateLabel;
  private String treeNodeDeleteLabel;
  private String treeNodeUpdateLabel;
  private String treeNodeUpdateLabelLabel;
  private String treeNodeItemAddLabel;
  private String treeNodeItemRemoveLabel;
  private String treeNodeLabel = "Node";
  private int columnCount = 0;

  public ManagedTree() {
    // register self as a listener of data ready events
    DataReadyEventBus.getInstance().addHandler(TreeEvent.TYPE, this);
    // register self as a listener of tree node events
    DataReadyEventBus.getInstance().addHandler(TreeNodeEvent.TYPE, this);
    initWidget(rootSP);
  }

  /**
   * Sets the data for the tree to be rendered.
   */
  public void setTreeCache(TreeCache treeCache) {
    this.treeCache = treeCache;
  }

  /**
   * Enables creating of a new tree node under an existing node
   */
  public void enableCreateTreeNode(String label) {
    this.treeNodeCreateLabel = label;
    this.treeNodeCreateEnabled = true;
  }

  /**
   * Enables delete of a node. Deletes the entire subtree
   */

  public void enableDeleteTreeNode(String label) {
    this.treeNodeDeleteLabel = label;
    this.treeNodeDeleteEnabled = true;
  }

  /**
   * Enables view and/or edit of node details. The details appear in the dialog
   * box that is passed in
   */
  public void enableUpdateTreeNode(String label,
      AbstractTreeNodeUpdateDialogBox treeNodeUpdateDialogBox) {
    this.treeNodeUpdateLabel = label;
    this.treeNodeUpdateEnabled = true;
    this.treeNodeUpdateDialogBox = treeNodeUpdateDialogBox;
  }

  /**
   * Enables quick edit of the node label
   */
  public void enableUpdateTreeNodeLabel(String label) {
    this.treeNodeUpdateLabelLabel = label;
    this.treeNodeUpdateLabelEnabled = true;
  }

  /**
   * Enables addition of an item to a tree node. The item is not another tree
   * node.
   */
  public void enableAddTreeNodeItem(String label,
      AbstractSelectItemDialogBox selectItemDialogBox) {
    this.treeNodeItemAddLabel = label;
    this.treeNodeItemAddEnabled = true;
    this.selectItemDialogBox = selectItemDialogBox;
    selectItemDialogBox.addCloseHandler(new SelectItemListLOVCloseHandler());
  }

  /**
   * Enables removal of an item from a tree node. The item is not another tree
   * node.
   */
  public void enableRemoveTreeNodeItem(String label) {
    this.treeNodeItemRemoveLabel = label;
    this.treeNodeItemDeleteEnabled = true;
  }

  /**
   * Sets the label of a tree node. Default is "Node"
   */
  public void setTreeNodeLabel(String treeNodeLabel) {
    this.treeNodeLabel = treeNodeLabel;
  }

  /**
   * Adds a column in the table in which items in a tree node are displayed
   */
  public void addTableColumn(TextColumn<ItemDTO> column, String header) {
    cellTable.addColumn(column, header);
    columnCount++;
  }

  /**
   * Sets the height of the split panel in which the tree and table for tree
   * node items are rendered.
   */
  public void setSplitPanelHeight(int splitPanelHeight) {
    rootSP.setSplitPanelHeight(splitPanelHeight);
  }

  /**
   * Renders the ManagedTree widget.
   */
  public void renderUI() {
    cellTable.setWidth("99%");
    cellTable.setStyleName("ya-dataTable");
    addTableActions();
    noItemsPanel.add(new Spacer(10, 20));
    Label noItemsLabel = new Label("No Items");
    noItemsPanel.add(noItemsLabel);
    noItemsPanel.setCellVerticalAlignment(noItemsLabel,
        HasVerticalAlignment.ALIGN_BOTTOM);
    VerticalPanel centerPanel = new VerticalPanel();
    centerPanel.setWidth("100%");
    centerPanel.add(cellTable);
    centerPanel.add(noItemsPanel);
    rootSP.setActionWidget(getTreeActions());
    rootSP.addWest(tree, 200);
    rootSP.add(centerPanel);

    // tree.addOpenHandler(new OpenHandler<TreeItem>() {
    // @Override
    // public void onOpen(OpenEvent<TreeItem> event) {
    // TreeNodeDTO treeNodeDTO = (TreeNodeDTO) event.getTarget()
    // .getUserObject();
    // if (treeNodeDTO != null)
    // // code for lazy loading would go here
    // }
    // });
    tree.addSelectionHandler(new SelectionHandler<TreeItem>() {
      @Override
      public void onSelection(SelectionEvent<TreeItem> event) {
        TreeItem selectedTreeItemWidget = event.getSelectedItem();
        if (selectedTreeItemWidget != updateLabelStateTreeItemWidget)
          resetAnyUpdateLabelState();
        TreeNodeDTO treeNodeDTO = (TreeNodeDTO) selectedTreeItemWidget
            .getUserObject();
        if (treeNodeDTO.getItems() == null) {
          cellTable.setRowCount(0);
          noItemsPanel.setVisible(true);
          return;
        }
        cellTable.setRowData(0, treeNodeDTO.getItems());
        cellTable.setRowCount(treeNodeDTO.getItems().size());
        cellTable.redraw();
        noItemsPanel.setVisible(false);
      }
    });
    treeCache.fetch(); // TODO check for null
  }

  private HorizontalPanel getTreeActions() {
    HorizontalPanel hPanel = new HorizontalPanel();
    if (treeNodeCreateEnabled) {
      addIcon(hPanel, Resources.I.getInlineCreate(), treeNodeCreateLabel,
          new TreeNodeCreateClickHandler());
    }
    if (treeNodeDeleteEnabled) {
      addIcon(hPanel, Resources.I.getInlineDelete(), treeNodeDeleteLabel,
          new TreeNodeDeleteClickHandler());
    }
    if (treeNodeUpdateLabelEnabled) {
      addIcon(hPanel, Resources.I.getInlineUpdate(), treeNodeUpdateLabelLabel,
          new TreeNodeUpdateLabelClickHandler());
    }
    if (treeNodeUpdateEnabled) {
      addIcon(hPanel, Resources.I.getInlineRetrieve(), treeNodeUpdateLabel,
          new TreeNodeUpdateClickHandler());
    }
    if (treeNodeItemAddEnabled) {
      addIcon(hPanel, Resources.I.getInlineObject(), treeNodeItemAddLabel,
          new TreeNodeItemAddClickHandler());
    }
    return hPanel;
  }

  private void addIcon(HorizontalPanel hPanel, ImageResource ir, String label,
      ClickHandler handler) {
    hPanel.add(new Spacer(5, 1));
    Image icon = new Image(ir);
    icon.setTitle(label);
    icon.addStyleName("ya-tree-action");
    if (handler != null)
      icon.addClickHandler(handler);
    hPanel.add(icon);
  }

  private void addTableActions() {
    if (treeNodeItemDeleteEnabled) {
      ImageActionCell<ItemDTO> imageActionCell = new ImageActionCell<ItemDTO>(
          Resources.I.getInlineDelete(),
          new ImageActionCell.Delegate<ItemDTO>() {
            @Override
            public void execute(ItemDTO itemDTO) {
              TreeItem selectedTreeItemWidget = (TreeItem) tree
                  .getSelectedItem();
              TreeNodeDTO selectedTreeNodeDTO = (TreeNodeDTO) selectedTreeItemWidget
                  .getUserObject();
              treeCache.removeItem(selectedTreeNodeDTO, itemDTO);
            }
          });

      Column<ItemDTO, ItemDTO> column = new Column<ItemDTO, ItemDTO>(
          imageActionCell) {
        @Override
        public ItemDTO getValue(ItemDTO item) {
          return item;
        }
      };

      cellTable.addColumn(column, "");
      // TODO P3: cursor: pointer in css not effective here
      cellTable.addColumnStyleName(columnCount++, "ya-tree-table-action");
      // TODO P3: cannot set treeNodeItemRemoveLabel as alt
    }
  }

  /**
   * Gets called when data arrives to the client asynchronously
   */
  @Override
  public void onTreeEvent(TreeEvent event) {
    String eventName = event.getTreeCache().getClass().getName();
    String objName = treeCache.getClass().getName();
    if (objName != eventName)
      return;
    log.info("ItemListEvent caught by ManagedTree: " + eventName);
    rootSP.show();
    populateTree();
  }

  private void populateTree() {
    TreeNodeDTO rootTreeNodeDTO = treeCache.getRootNode();
    if (rootTreeNodeDTO == null) {
      log.severe("renderTree: Null root node not expected but found!");
      return;
    }

    TreeItem rootTreeItemWidget = new TreeItem(rootTreeNodeDTO.getLabel());
    rootTreeItemWidget.setUserObject(rootTreeNodeDTO);
    tree.addItem(rootTreeItemWidget);
    tree.setSelectedItem(rootTreeItemWidget);
    recurse(rootTreeItemWidget);
    rootTreeItemWidget.setState(true);
  }

  private void recurse(TreeItem treeItemWidget) {
    TreeNodeDTO treeNodeDTO = (TreeNodeDTO) treeItemWidget.getUserObject();

    if (treeNodeDTO.getChildren() != null)
      for (TreeNodeDTO childTreeNodeDTO : treeNodeDTO.getChildren()) {
        TreeItem childTreeItemWidget = new TreeItem(childTreeNodeDTO.getLabel());
        childTreeItemWidget.setUserObject(childTreeNodeDTO);
        treeItemWidget.addItem(childTreeItemWidget);
        recurse(childTreeItemWidget);
      }
  }

  @Override
  public void onTreeNodeEvent(TreeNodeEvent event) {
    if (!event.getTreeCacheClassName().equals(treeCache.getClass().getName()))
      return;
    log.info("TreeNodeEvent caught by ManagedTree: "
        + event.getTreeNodeAction() + " " + event.getTreeCacheClassName());
    switch (event.getTreeNodeAction()) {
    case Create:
      createTreeItemWidget(event.getTreeNode());
      break;
    case Delete:
      deleteTreeItemWidget(event.getTreeNode());
      break;
    case Update:
      updateTreeItemWidget(event.getTreeNode());
      break;
    case UpdateLabel:
      updateTreeItemWidgetLabel(event.getTreeNode());
      break;
    case AddItem:
      addTreeNodeItem(event.getTreeNode());
      break;
    case RemoveItem:
      removeTreeNodeItem(event.getTreeNode());
      break;
    }
  }

  private void createTreeItemWidget(TreeNodeDTO createdTreeNodeDTO) {
    TreeItem parentTreeItemWidget = findTreeItemWidget(tree.getItem(0),
        createdTreeNodeDTO.getParent());
    TreeItem createdTreeItemWidget = new TreeItem(createdTreeNodeDTO.getLabel());
    createdTreeItemWidget.setUserObject(createdTreeNodeDTO);
    parentTreeItemWidget.addItem(createdTreeItemWidget);
    tree.setSelectedItem(createdTreeItemWidget);
    tree.ensureSelectedItemVisible();
  }

  private void deleteTreeItemWidget(TreeNodeDTO deletedTreeNodeDTO) {
    TreeItem parentTreeItemWidget = findTreeItemWidget(tree.getItem(0),
        deletedTreeNodeDTO.getParent());
    int i = 0;
    for (; i < parentTreeItemWidget.getChildCount(); i++)
      if (parentTreeItemWidget.getChild(i).getUserObject() == deletedTreeNodeDTO)
        break;
    if (i >= parentTreeItemWidget.getChildCount())
      return;
    parentTreeItemWidget.removeItem(parentTreeItemWidget.getChild(i));
    if (i > 0)
      tree.setSelectedItem(parentTreeItemWidget.getChild(i - 1));
    else
      tree.setSelectedItem(parentTreeItemWidget);
  }

  private void updateTreeItemWidget(TreeNodeDTO updatedTreeNodeDTO) {
    treeNodeUpdateDialogBox.setSuccessMessage();
    Timer t = new Timer() {
      @Override
      public void run() {
        treeNodeUpdateDialogBox.hide();
      }
    };
    t.schedule(2000);
  }

  private void updateTreeItemWidgetLabel(TreeNodeDTO updatedTreeNodeDTO) {
    resetAnyUpdateLabelState();
  }

  private void addTreeNodeItem(TreeNodeDTO parentTreeNodeDTO) {
    cellTable.setRowData(0, parentTreeNodeDTO.getItems());
    cellTable.setRowCount(parentTreeNodeDTO.getItems().size());
    cellTable.redraw();
    noItemsPanel.setVisible(false);
  }

  private void removeTreeNodeItem(TreeNodeDTO parentTreeNodeDTO) {
    if (parentTreeNodeDTO.getItems() == null) {
      cellTable.setRowCount(0);
      noItemsPanel.setVisible(true);
      return;
    }
    cellTable.setRowData(0, parentTreeNodeDTO.getItems());
    cellTable.setRowCount(parentTreeNodeDTO.getItems().size());
    cellTable.redraw();
  }

  private TreeItem findTreeItemWidget(TreeItem treeItemWidget,
      TreeNodeDTO treeNodeDTO) {
    if (treeItemWidget.getUserObject() == treeNodeDTO)
      return treeItemWidget;
    for (int i = 0; i < treeItemWidget.getChildCount(); i++) {
      TreeItem childTreeItemWidget = treeItemWidget.getChild(i);
      TreeItem foundTreeItemWidget = findTreeItemWidget(childTreeItemWidget,
          treeNodeDTO);
      if (foundTreeItemWidget != null)
        return foundTreeItemWidget;
    }
    return null;
  }

  private class TreeNodeCreateClickHandler implements ClickHandler {
    @Override
    public void onClick(ClickEvent event) {
      resetAnyUpdateLabelState();
      TreeItem selectedTreeItemWidget = (TreeItem) tree.getSelectedItem();
      TreeNodeDTO selectedTreeNodeDTO = (TreeNodeDTO) selectedTreeItemWidget
          .getUserObject();
      treeCache.createNode(selectedTreeNodeDTO, "Child " + treeNodeLabel);
    }
  }

  private class TreeNodeDeleteClickHandler implements ClickHandler {
    @Override
    public void onClick(ClickEvent event) {
      resetAnyUpdateLabelState();
      TreeItem selectedTreeItemWidget = (TreeItem) tree.getSelectedItem();
      TreeNodeDTO selectedTreeNodeDTO = (TreeNodeDTO) selectedTreeItemWidget
          .getUserObject();
      if (selectedTreeNodeDTO.getParent() == null) {
        confDialog.message("Cannot Delete Root " + treeNodeLabel);
        return;
      }
      if (selectedTreeNodeDTO.getChildren() != null
          || selectedTreeNodeDTO.getItems() != null)
        confDialog.delete();
      else
        treeCache.deleteNode(selectedTreeNodeDTO);
    }
  }

  private class TreeNodeUpdateClickHandler implements ClickHandler {
    @Override
    public void onClick(ClickEvent event) {
      resetAnyUpdateLabelState();
      TreeItem selectedTreeItemWidget = (TreeItem) tree.getSelectedItem();
      treeNodeUpdateDialogBox.treeNodeDTO = (TreeNodeDTO) selectedTreeItemWidget
          .getUserObject();
      treeNodeUpdateDialogBox.populate();
      treeNodeUpdateDialogBox.center();
    }
  }

  private class TreeNodeUpdateLabelClickHandler implements ClickHandler {
    @Override
    public void onClick(ClickEvent event) {
      if (updateLabelStateTreeItemWidget != null)
        return;
      final TreeItem selectedTreeItemWidget = (TreeItem) tree.getSelectedItem();
      final TreeNodeDTO selectedTreeNodeDTO = (TreeNodeDTO) selectedTreeItemWidget
          .getUserObject();
      final TextBox textBox = new TextBox();
      updateLabelStateTreeItemWidget = selectedTreeItemWidget;
      textBox.setText(selectedTreeNodeDTO.getLabel());
      HorizontalPanel hPanel = new HorizontalPanel();
      Image saveI = new Image(Resources.I.getInlineSave());
      Image cancelI = new Image(Resources.I.getInlineDelete());
      hPanel.add(textBox);
      hPanel.add(new Spacer(5, 1));
      hPanel.add(saveI);
      hPanel.add(new Spacer(5, 1));
      hPanel.add(cancelI);
      textBox.setWidth("100px");
      saveI.setTitle("Save");
      cancelI.setTitle("Cancel");
      saveI.addClickHandler(new ClickHandler() {
        @Override
        public void onClick(ClickEvent event) {
          treeCache.updateNodeLabel(selectedTreeNodeDTO, textBox.getValue());
        }
      });
      cancelI.addClickHandler(new ClickHandler() {
        @Override
        public void onClick(ClickEvent event) {
          resetAnyUpdateLabelState();
        }
      });

      selectedTreeItemWidget.setWidget(hPanel);
    }
  }

  private TreeItem updateLabelStateTreeItemWidget;

  private void resetAnyUpdateLabelState() {
    if (updateLabelStateTreeItemWidget == null)
      return;
    TreeNodeDTO updateLabelStateTreeNodeDTO = (TreeNodeDTO) updateLabelStateTreeItemWidget
        .getUserObject();
    updateLabelStateTreeItemWidget.setText(updateLabelStateTreeNodeDTO
        .getLabel());
    updateLabelStateTreeItemWidget = null;
  }

  private class TreeNodeItemAddClickHandler implements ClickHandler {
    @Override
    public void onClick(ClickEvent event) {
      resetAnyUpdateLabelState();
      selectItemDialogBox.center();
      // addObjectLOV.setPopupPositionAndShow(new PositionCallback() {
      // @Override
      // public void setPosition(int offsetWidth, int offsetHeight) {
      // int left = (Window.getClientWidth() - offsetWidth) / 3;
      // int top = (Window.getClientHeight() - offsetHeight) / 3;
      // addObjectLOV.setPopupPosition(left, top);
      // }
      // });
    }
  }

  private class SelectItemListLOVCloseHandler implements
      CloseHandler<PopupPanel> {

    @Override
    public void onClose(CloseEvent<PopupPanel> event) {
      ItemDTO selectedItemDTO = selectItemDialogBox.getSelectedItem();

      if (selectedItemDTO == null) { // user canceled the pop-up
        TreeItem selectedTreeItemWidget = (TreeItem) tree.getSelectedItem();
        TreeNodeDTO selectedTreeNodeDTO = (TreeNodeDTO) selectedTreeItemWidget
            .getUserObject();
        selectedTreeItemWidget.setText(selectedTreeNodeDTO.getLabel());
        return;
      }

      TreeItem selectedTreeItemWidget = (TreeItem) tree.getSelectedItem();
      TreeNodeDTO selectedTreeNodeDTO = (TreeNodeDTO) selectedTreeItemWidget
          .getUserObject();
      treeCache.addItem(selectedTreeNodeDTO, selectedItemDTO);
    }
  }

  private class ConfirmDialogBox extends DialogBox {
    private Button deleteB = new Button("Delete");
    private Button cancelB = new Button("Cancel");

    private void delete() {
      setText("Confirm Delete");
      deleteB.setVisible(true);
      cancelB.setText("Cancel");
      confDialog.center();
    }

    private void message(String m) {
      setText(m);
      deleteB.setVisible(false);
      cancelB.setText("Okay");
      confDialog.center();
    }

    private ConfirmDialogBox() {
      // setText("Confirm Delete");
      setAnimationEnabled(true);
      HorizontalPanel hPanel = new HorizontalPanel();
      hPanel.add(deleteB);
      hPanel.add(cancelB);
      setWidget(hPanel);
      deleteB.addClickHandler(new ClickHandler() {
        @Override
        public void onClick(ClickEvent event) {
          TreeItem selectedTreeItemWidget = (TreeItem) tree.getSelectedItem();
          TreeNodeDTO selectedTreeNodeDTO = (TreeNodeDTO) selectedTreeItemWidget
              .getUserObject();
          treeCache.deleteNode(selectedTreeNodeDTO);
          ConfirmDialogBox.this.hide();
        }
      });
      cancelB.addClickHandler(new ClickHandler() {
        @Override
        public void onClick(ClickEvent event) {
          ConfirmDialogBox.this.hide();
        }
      });
    }
  }

  public abstract class AbstractTreeNodeUpdateDialogBox extends DialogBox {
    private TreeNodeDTO treeNodeDTO;

    public TreeNodeDTO getTreeNodeDTO() {
      return treeNodeDTO;
    }

    public abstract void populate();

    public abstract void setSuccessMessage();

    public abstract void setErrorMessage(String errorMessage);
  }

}
